package com.wutong.resonance.model.audio;

import com.wutong.resonance.model.audio.enums.AudioType;
import com.wutong.resonance.model.exception.AudioException;

import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioHeader;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.audio.flac.FlacFileReader;
import org.jaudiotagger.audio.generic.AudioFileReader;
import org.jaudiotagger.audio.mp3.MP3FileReader;
import org.jaudiotagger.audio.ogg.OggFileReader;
import org.jaudiotagger.audio.wav.WavFileReader;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.TagException;

/**
 * 音频参数类
 *
 * @author wutong
 */
@Slf4j
@Getter
@Setter
public class AudioParams {

    /** 单声道 */
    private static final int MONO = 1;
    /** 立体声 */
    private static final int STEREO = 2;

    // 常用音频参数
    /** 单声道，PCM，8K，16位，有符号，小端 */
    private static final AudioParams MONO_PCM_48K_16B =
            new AudioParams(48000, 16, MONO, true, ByteOrder.LITTLE_ENDIAN);
    /** 单声道，PCM，44K，16位，有符号，小端 */
    private static final AudioParams MONO_PCM_44K_16B =
            new AudioParams(44100, 16, MONO, true, ByteOrder.LITTLE_ENDIAN);
    /** 单声道，PCM，22K，16位，有符号，小端 */
    private static final AudioParams MONO_PCM_22K_16B =
            new AudioParams(22050, 16, MONO, true, ByteOrder.LITTLE_ENDIAN);
    /** 单声道，PCM，11K，16位，有符号，小端 */
    private static final AudioParams MONO_PCM_11K_16B =
            new AudioParams(11025, 16, MONO, true, ByteOrder.LITTLE_ENDIAN);

    /** 单声道，PCM，48K，16位，有符号，大端 */
    private static final AudioParams MONO_PCM_48K_16B_BE =
            new AudioParams(48000, 16, MONO, true, ByteOrder.BIG_ENDIAN);

    /** 单声道，PCM，44.1K，16位，有符号，大端 */
    private static final AudioParams MONO_PCM_44K_16B_BE =
            new AudioParams(44100, 16, MONO, true, ByteOrder.BIG_ENDIAN);

    /** 单声道，PCM，48K，24位，有符号，小端 */
    private static final AudioParams MONO_PCM_48K_24B =
            new AudioParams(48000, 24, MONO, true, ByteOrder.LITTLE_ENDIAN);

    /** 单声道，PCM，44.1K，24位，有符号，小端 */
    private static final AudioParams MONO_PCM_44K_24B =
            new AudioParams(44100, 24, MONO, true, ByteOrder.LITTLE_ENDIAN);

    /** 单声道，PCM，48K，24位，有符号，大端 */
    private static final AudioParams MONO_PCM_48K_24B_BE =
            new AudioParams(48000, 24, MONO, true, ByteOrder.BIG_ENDIAN);

    /** 单声道，PCM，44.1K，24位，有符号，大端 */
    private static final AudioParams MONO_PCM_44K_24B_BE =
            new AudioParams(44100, 24, MONO, true, ByteOrder.BIG_ENDIAN);

    /** 单声道，PCM，48K，16位，有符号，小端 */
    public static final AudioParams STEREO_PCM_48K_16B =
            new AudioParams(48000, 16, STEREO, true, ByteOrder.LITTLE_ENDIAN);
    /** 单声道，PCM，44K，16位，有符号，小端 */
    public static final AudioParams STEREO_PCM_44K_16B =
            new AudioParams(44100, 16, STEREO, true, ByteOrder.LITTLE_ENDIAN);
    /** 单声道，PCM，22K，16位，有符号，小端 */
    public static final AudioParams STEREO_PCM_22K_16B =
            new AudioParams(22050, 16, STEREO, true, ByteOrder.LITTLE_ENDIAN);
    /** 单声道，PCM，11K，16位，有符号，小端 */
    public static final AudioParams STEREO_PCM_11K_16B =
            new AudioParams(11025, 16, STEREO, true, ByteOrder.LITTLE_ENDIAN);

    /** 立体声，PCM，48K，16位，有符号，大端 */
    public static final AudioParams STEREO_PCM_48K_16B_BE =
            new AudioParams(48000, 16, STEREO, true, ByteOrder.BIG_ENDIAN);

    /** 立体声，PCM，44.1K，16位，有符号，大端 */
    public static final AudioParams STEREO_PCM_44K_16B_BE =
            new AudioParams(44100, 16, STEREO, true, ByteOrder.BIG_ENDIAN);

    /** 立体声，PCM，48K，24位，有符号，小端 */
    public static final AudioParams STEREO_PCM_48K_24B =
            new AudioParams(48000, 24, STEREO, true, ByteOrder.LITTLE_ENDIAN);

    /** 立体声，PCM，44.1K，24位，有符号，小端 */
    public static final AudioParams STEREO_PCM_44K_24B =
            new AudioParams(44100, 24, STEREO, true, ByteOrder.LITTLE_ENDIAN);

    /** 立体声，PCM，48K，24位，有符号，大端 */
    public static final AudioParams STEREO_PCM_48K_24B_BE =
            new AudioParams(48000, 24, STEREO, true, ByteOrder.BIG_ENDIAN);

    /** 立体声，PCM，44.1K，24位，有符号，大端 */
    public static final AudioParams STEREO_PCM_44K_24B_BE =
            new AudioParams(44100, 24, STEREO, true, ByteOrder.BIG_ENDIAN);

    /** 采样率，单位：赫兹 (Hz) */
    private int sampleRate;
    /** 位深度，单位：比特 (bits) */
    private int bitDepth;
    /** 声道数 */
    private int channels;
    /** 比特率，单位：千比特/秒 (Kbps) */
    private long bitRate;
    /** 字节率，单位：字节/秒 (B/s) */
    private int byteRate;
    /** 是否有符号 */
    private boolean signed;
    /** 是否大端 */
    private ByteOrder bigEndian;
    /**
     * 其他音频头信息 如：MP3文件中包含的VBR信息等
     *
     * <p>注意：该字段仅在读取音频文件时有效，其他情况下该字段为null
     */
    private AudioHeader otherAudioHeader;

    private File audioFile;

    /** 帧大小 单位：字节 (B) */
    private int frameSize;

    /**
     * 构造函数
     *
     * @param sampleRate 采样率，单位：赫兹 (Hz)
     * @param bitDepth   位深度，单位：比特 (bits)
     * @param channels   声道数
     * @param signed     是否有符号
     * @param bigEndian  是否大端 (Wav文件使用Little Endian)
     */
    public AudioParams(
            int sampleRate, int bitDepth, int channels, boolean signed, ByteOrder bigEndian) {
        this.sampleRate = sampleRate;
        this.bitDepth = bitDepth;
        this.channels = channels;
        this.signed = signed;
        this.bigEndian = bigEndian;
        this.bitRate = (long) sampleRate * bitDepth * channels / 1000;
        this.byteRate = sampleRate * bitDepth * channels / 8;
        this.frameSize = channels * bitDepth / 8;
        this.audioFile = null;
    }

    /**
     * 构造函数
     *
     * @param format Java Sound API的音频格式
     */
    public AudioParams(AudioFormat format) {
        this.sampleRate = (int) format.getSampleRate();
        this.bitDepth = format.getSampleSizeInBits();
        this.channels = format.getChannels();
        this.signed = format.getEncoding().toString().toLowerCase().contains("signed");
        this.bigEndian = format.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
        this.bitRate = (long) sampleRate * bitDepth * channels / 1000;
        this.byteRate = sampleRate * bitDepth * channels / 8;
        this.frameSize = channels * bitDepth / 8;
        this.audioFile = null;
    }

    /**
     * 构造函数
     *
     * @param audioFile 音频文件
     */
    public AudioParams(File audioFile) {
        this.audioFile = audioFile;
        String type = audioFile.getName().substring(audioFile.getName().lastIndexOf(".") + 1);
        AudioFileReader reader =
                switch (type) {
                    case "mp3" -> new MP3FileReader();
                    case "wav" -> new WavFileReader();
                    case "flac" -> new FlacFileReader();
                    case "aac" -> new OggFileReader();
                    default -> {
                        log.error("不支持的音频文件类型: {}", type);
                        throw new AudioException.AudioFileLoadException("不支持的音频文件类型: " + type);
                    }
                };
        try {
            AudioFile file = reader.read(audioFile);
            log.debug("读取音频文件成功: {}", audioFile.getName());
            this.otherAudioHeader = file.getAudioHeader();
            this.sampleRate = this.otherAudioHeader.getSampleRateAsNumber();
            this.bitDepth = this.otherAudioHeader.getBitsPerSample();
            this.channels = Integer.parseInt(this.otherAudioHeader.getChannels());
            this.bitRate = this.otherAudioHeader.getBitRateAsNumber();
            this.byteRate = this.otherAudioHeader.getByteRate();

            AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(audioFile);
            AudioFormat format = audioInputStream.getFormat();
            this.signed = format.getEncoding().toString().toLowerCase().contains("signed");
            this.bigEndian = format.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
            audioInputStream.close();
            log.debug("通过AudioSystem读取音频文件成功载入音频参数: {}", this);
        } catch (CannotReadException
                 | UnsupportedAudioFileException
                 | IOException
                 | TagException
                 | ReadOnlyFileException
                 | InvalidAudioFrameException e) {
            log.error("读取音频文件失败[{}]: {}", e.getClass().getSimpleName(), e.getMessage());
            throw new AudioException.AudioFileLoadException("读取音频文件失败: " + e.getMessage());
        }
    }

    /**
     * 转为Java Sound API的AudioFormat对象
     *
     * @return AudioFormat对象
     */
    public AudioFormat toAudioFormat() {
        return new AudioFormat(
                sampleRate, bitDepth, channels, signed, bigEndian == ByteOrder.BIG_ENDIAN);
    }

    /**
     * 加载音频文件信息
     *
     * @return 音频文件信息
     */
    public AudioFileInfo loadAudioFileInfo() {
        return new AudioFileInfo(getAudioFile());
    }

    /**
     * 加载音频文件信息
     *
     * @param audioFile 音频文件
     *
     * @return 音频文件信息
     */
    public static AudioFileInfo loadAudioFileInfo(File audioFile) {
        return new AudioFileInfo(audioFile);
    }

    public File getAudioFile() {
        if (audioFile == null) {
            log.error("通过构造函数AudioParams(int sampleRate, int bitDepth, int channels, boolean signed, ByteOrder bigEndian)创建的AudioParams对象没有音频文件");
            throw new AudioException.AudioFileLoadException("音频文件未加载：audioFile为null");
        }
        return audioFile;
    }

    @Override
    public String toString() {
        return "\n-----------------------\n"
                + "音频参数:\n"
                + "采样率: "
                + sampleRate
                + " Hz\n"
                + "位深度: "
                + bitDepth
                + " bits\n"
                + "声道数: "
                + channels
                + "\n"
                + "比特率: "
                + bitRate
                + " Kbps\n"
                + "字节率: "
                + byteRate
                + " B/s\n"
                + "是否有符号: "
                + signed
                + "\n"
                + "字节序模式: "
                + bigEndian
                + "\n"
                + "-----------------------";
    }

    @Slf4j
    @Setter
    @Getter
    public static class AudioFileInfo {
        private String fileName;
        private AudioType fileType;
        private long fileSize;

        private long duration;
        private String author;
        private String album;
        private String title;
        private String track;
        private String year;
        private String genre;
        private String comment;

        /**
         * 构造函数
         *
         * @param audioFile 音频文件
         */
        public AudioFileInfo(File audioFile) {
            this.fileName = audioFile.getName();
            this.fileType =
                    AudioType.getAudioType(
                            audioFile.getName().substring(audioFile.getName().lastIndexOf(".") + 1));
            this.fileSize = audioFile.length();

            AudioFileReader reader =
                    switch (this.fileType) {
                        case MP3 -> new MP3FileReader();
                        case WAV -> new WavFileReader();
                        case FLAC -> new FlacFileReader();
                        case AAC -> new OggFileReader();
                        default -> {
                            log.error("不支持的音频文件类型: {}", this.fileType);
                            throw new AudioException.AudioFileLoadException("不支持的音频文件类型: " + this.fileType);
                        }
                    };
            try {
                AudioFile file = reader.read(audioFile);
                log.debug("读取音频文件成功: {}", audioFile.getName());
                this.duration = file.getAudioHeader().getTrackLength();
                //                log.debug("FileTag: {}", file.getTag());
                this.author = file.getTag().getFirst(FieldKey.ARTIST).replace("\u0000", "");
                this.album = file.getTag().getFirst(FieldKey.ALBUM).replace("\u0000", "");
                this.title = file.getTag().getFirst(FieldKey.TITLE).replace("\u0000", "");
                this.track = file.getTag().getFirst(FieldKey.TRACK).replace("\u0000", "");
                this.year = file.getTag().getFirst(FieldKey.YEAR).replace("\u0000", "");
                this.genre = file.getTag().getFirst(FieldKey.GENRE).replace("\u0000", "");
                this.comment = file.getTag().getFirst(FieldKey.COMMENT).replace("\u0000", "");
            } catch (CannotReadException
                     | IOException
                     | TagException
                     | ReadOnlyFileException
                     | InvalidAudioFrameException e) {
                log.error("读取音频文件失败[{}]: {}", e.getClass().getSimpleName(), e.getMessage());
                throw new AudioException.AudioFileLoadException("读取音频文件失败: " + e.getMessage());
            }
        }

        /**
         * 返回对象的字符串表示形式。
         *
         * @return 该对象的字符串表示形式。
         * @apiNote 通常，{@code toString} 方法返回一个“以文本方式表示”该对象的字符串。结果应简明扼要且便于阅读。建议所有子类都覆盖此方法。字符串输出在不同时间或跨 JVM 调用时不一定稳定。
         * @implSpec 类 {@code Object} 的 {@code toString} 方法返回一个字符串，该字符串由对象所属的类的名称、at 符号 `{@code @}`
         * 和对象的哈希码的无符号十六进制表示组成。换句话说，此方法返回等于以下值的字符串：
         * <blockquote>
         * <pre>
         * getClass().getName() + '@' + Integer.toHexString(hashCode())
         * </pre>
         * </blockquote>
         */
        @Override
        public String toString() {
            return "\n-------------------------\n"
                    + "文件名: "
                    + fileName
                    + "\n"
                    + "文件类型: "
                    + fileType
                    + "\n"
                    + "文件大小: "
                    + fileSize
                    + " bytes\n"
                    + "时长: "
                    + duration
                    + " 秒\n"
                    + "作者: "
                    + author
                    + "\n"
                    + "专辑: "
                    + album
                    + "\n"
                    + "标题: "
                    + title
                    + "\n"
                    + "曲目: "
                    + track
                    + "\n"
                    + "年份: "
                    + year
                    + "\n"
                    + "风格: "
                    + genre
                    + "\n"
                    + "注释: "
                    + comment
                    + "\n";
        }
    }
}
